Sissejuhatus

Tänase praktikumi teema on graafide ja võrgustike visualiseerimine. Seda teeme me kasutades pakette tidygraph ja ggraph. Lisaks kasutamae andmestikku, kus on võetud viimase pooleteise aasta populaarsemad Eesti lood Spotifyst ja nende esitajate kohta tekitatud võrgustik vastavalt sellele kes on omavahel koostööd teinud. Iga artisti kohta on teada tema populaarsus (skaalas 0-100) ja jälgijate arv. Iga serva puhul on kokku loetud koostöös valminud lugude arv ja kõige populaarsema loo populaarsus.

Graafi objekt

Graafi objekti loomine

Graafide jaoks defineerib tidygraph pakett objekti tbl_graph, mis on sisuliselt kaks seotud tibble tüüpi objekti, üks tippude ja teine servade jaoks. Seda defineeritakse käsuga tbl_graph, millele tuleb ette anda kaks tibble või data.frame tüüpi objekti. See käsk ootab, et servade failis oleks kaks veergu: from ja to. Tipu nimed veergudes from ja to peavad olema olemas ka tippude tabelis mingi tunnusena, mille nimi antakse ette argumendiga node_key. Tuleks ka määrata, kas graaf on suunatud või suunamata, argumendiga directed.

edges_est = edges_est %>% 
  rename(from = ArtistName1, to = ArtistName2)

g = tbl_graph(nodes = nodes_est, edges = edges_est, node_key = "ArtistName", directed = F)
g 

Graafe saab luua ka kasutades naabrusmaatrikseid, kasutades paketi igraph fuktsiooni graph.adjacency ja muutes selle tbl_graph objektiks funktsiooniga as_tbl_graph.

library(igraph)

mat = cbind(c(0, 0, 1), c(1, 0, 1), c(1, 0, 0))
rownames(mat) = c("A", "B", "C")
colnames(mat) = c("A", "B", "C")
mat
  A B C
A 0 1 1
B 0 0 0
C 1 1 0
graph.adjacency(mat) %>% 
  as_tbl_graph()
# A tbl_graph: 3 nodes and 4 edges
#
# A directed simple graph with 1 component
#
# Node Data: 3 x 1 (active)
  name 
  <chr>
1 A    
2 B    
3 C    
#
# Edge Data: 4 x 2
   from    to
  <int> <int>
1     1     2
2     1     3
3     3     1
# … with 1 more row

Graafide manipuleerimine

Graafi objekti manipuleerimine tidygraph paketis on väga sarnane tavaliste andmetabelite töötlemisega tidyverse abil. Kuna aga graafi objekt koosneb kahest andmetabelist, siis on lisatud käsk nimega activate, millega saab ette öelda graaf, kas me plaanime rakendada käske tippude või servade tabelile. Vastavalt siis activate(nodes) ja activate(edges) aktiveerivad vastavalt tipud ja servad. Peale seda saame rakendada tuttavaid käske nagu näiteks mutate.

g %>% 
  activate(edges) %>% 
  mutate(VeryPopular = maxPopularity > 40)

g %>% 
  activate(nodes) %>% 
  mutate(VeryPopular = ArtistPopularity > 45)

Kui filtreerida tippusid, siis on sellel tagajärjed ka servadele. Nimelt kustutatakse ära ka servad, mille puhul vähemalt üks tipp andemtest välja visatakse. See on mugav, sest nii ei pea muretsema, et kaks tabelit sünkroonist välja lähevad.

g %>% 
  activate(nodes) %>% 
  mutate(VeryPopular = ArtistPopularity > 45) %>%
  filter(VeryPopular == T)

Erinevate tidyverse funktsioonide kasutamisel on loomulikult piirid. Näiteks summarize on käsk mille käitumist graafil ei ole võimalik üksühele üle kanda ja seega see antud juhul ei tööta. Küll aga töötavad kõik *_join funktsioonid ja on defineeritud graafide liitmine bind_graph abil. Loe lähemalt lingilt https://www.data-imaginist.com/2017/introducing-tidygraph/.

Ülesanded

Loome kaks tabelit

n = tibble(
  Name = c("A", "B", "C", "D"),
  Value = 1:4,
  Class = c("Class1", "Class2", "Class2", "Class2")
)

e = tibble(
  Name1 = c("A", "A", "B", "C", "C", "D"),
  Name2 = c("C", "D", "A", "A", "B", "B")
)
e <- e %>% 
  rename(from = Name1, to = Name2)

tbl_graph$nodes(nodes =e, node_key = )
  • Nende tabelite põhjal looge tbl_graph tüüpi objekt. Mõelge, kas antud graaf peaks olema suunatud või suunamata

  • Saadud graafist filtreerige välja alamgraaf mis sisaldab ainult klassi “Class2” kuuluvaid tippe.

Graafi joonistamine

Graafe joonistame selles praktikumis paketiga ggraph, mis on sisliselt laiendus ggplot2-le graafi andmete jaoks. Graafik luukase siin käsuga ggraph millele hakatakse siis graafiku elemente liitma. Tippude joonistamiseks on funktsioonid geom_node_* ja servade jaoks geom_edge_*. Andmepunktide kujutused graafilistele parameetritele antekse ette geom_ funktsioonide sees, sest me võime tahta sama parameetrit (näit. värv) defineerida nii tippudele kui servadele.

ggraph(g) +
  geom_node_point() +
  geom_edge_link()

Antud graaf ei näe väga hea välja, sest automaatselt valitud punktide paigutuse algoritm (stress), ei ole optimaalne. Punktide paigutust võib kontrollida parameetriga layout.

Esiteks võib sellele ette anda ühe mitmest paigutus algoritmist, millest mõned on järgnevad:

Paigutuse võib hea tahtmise korral ka ise ette anda maatriksina.

g  
# A tbl_graph: 87 nodes and 74 edges
#
# An undirected simple graph with 34 components
#
# Node Data: 87 x 4 (active)
  ArtistName ArtistPopularity ArtistFollowers ArtistGenre      
  <chr>                 <dbl>           <dbl> <chr>            
1 Pluuto                   42            4231 "estonian hip ho…
2 Nublu                    55           37366 "estonian pop"   
3 5MIINUST                 45           21454 "estonian hip ho…
4 PLUUTO                   25              71 ""               
5 Reket                    48           13520 "estonian hip ho…
6 NOËP                     53           23581 "estonian electr…
# … with 81 more rows
#
# Edge Data: 74 x 4
   from    to nTracks maxPopularity
  <int> <int>   <int>         <dbl>
1    23    24       1            34
2     2    23       1            34
3     3    58       1            44
# … with 71 more rows
lo = cbind(runif(87), runif(87))

lo
             [,1]        [,2]
 [1,] 0.662596733 0.075323027
 [2,] 0.269026553 0.889997697
 [3,] 0.135550009 0.029140577
 [4,] 0.169536707 0.507268359
 [5,] 0.302969098 0.910015675
 [6,] 0.936581166 0.147695661
 [7,] 0.693252315 0.607692787
 [8,] 0.845766872 0.675253721
 [9,] 0.880355598 0.987907342
[10,] 0.029445802 0.368267840
[11,] 0.222628514 0.102911692
[12,] 0.326749816 0.664828027
[13,] 0.009221235 0.447287791
[14,] 0.221918406 0.777034756
[15,] 0.236033614 0.625638479
[16,] 0.877495246 0.927023692
[17,] 0.632426293 0.401834335
[18,] 0.546805055 0.408971063
[19,] 0.411800624 0.607091832
[20,] 0.770410382 0.964285584
[21,] 0.379112357 0.090026133
[22,] 0.546063718 0.107978636
[23,] 0.897821723 0.730566913
[24,] 0.610101395 0.662014036
[25,] 0.351345219 0.975902406
[26,] 0.056662203 0.140464226
[27,] 0.214647673 0.217428566
[28,] 0.022600331 0.571534687
[29,] 0.298666016 0.155996276
[30,] 0.381898565 0.130839787
[31,] 0.638748182 0.524384490
[32,] 0.879089025 0.352608104
[33,] 0.173024775 0.921068273
[34,] 0.898233602 0.923170586
[35,] 0.233122667 0.150494365
[36,] 0.686841902 0.446558392
[37,] 0.981642799 0.758673116
[38,] 0.736455046 0.188965944
[39,] 0.398220531 0.003930629
[40,] 0.919550775 0.290675855
[41,] 0.745733682 0.458774625
[42,] 0.993803212 0.680372039
[43,] 0.704944525 0.612356485
[44,] 0.107252625 0.147779734
[45,] 0.337077885 0.733460389
[46,] 0.615534944 0.626856267
[47,] 0.704329374 0.252769461
[48,] 0.597881675 0.687044830
[49,] 0.229773201 0.644919660
[50,] 0.334824573 0.295184639
[51,] 0.558033166 0.920886023
[52,] 0.519211445 0.876314067
[53,] 0.778482103 0.509269712
[54,] 0.038757446 0.303369572
[55,] 0.650278749 0.747693845
[56,] 0.967808788 0.767233180
[57,] 0.427266140 0.419479232
[58,] 0.851476549 0.829745990
[59,] 0.359234563 0.193245298
[60,] 0.200083146 0.493635337
[61,] 0.354128396 0.410249556
[62,] 0.487300077 0.952217243
[63,] 0.415436797 0.435679120
[64,] 0.624097493 0.108958049
[65,] 0.887584910 0.830717738
[66,] 0.703132574 0.866754119
[67,] 0.735427797 0.755324924
[68,] 0.481408453 0.976971670
[69,] 0.023100206 0.945993355
[70,] 0.776635162 0.965128212
[71,] 0.822792752 0.111939796
[72,] 0.827775015 0.537961433
[73,] 0.340784257 0.639176049
[74,] 0.400972096 0.482579273
[75,] 0.647671234 0.976453587
[76,] 0.674764991 0.923791490
[77,] 0.282198098 0.505036931
[78,] 0.530972903 0.330972680
[79,] 0.549650805 0.694506390
[80,] 0.878241215 0.184076820
[81,] 0.468372708 0.726386093
[82,] 0.075580038 0.654911363
[83,] 0.518065521 0.587482974
[84,] 0.446811819 0.729395255
[85,] 0.531445435 0.857632499
[86,] 0.209030181 0.568405746
[87,] 0.873584309 0.632924577
ggraph(g, layout = lo) +
  geom_node_point() +
  geom_edge_link()

Tippude näitamisel, peale punktide väga häid võimalusi pole. Küll aga on erinevaid võimalusi servade joonistamiseks. Kõige kasutatavam on juba eelpool kasutatud geom_edge_link, mis tõmbab tippude vahele sirged jooned. Teised võimalused on veel:

Kui graafid on suunatud saab joontele lisada ka nooli, mida saab spetsifitseerida argumendiga arrow, mis omamkorda võtab väärtuseks arrow funktsiooni grid paketist.

Graafil meta-andmete näitamine

Siiani näidatud graafikud on olnud väga mustvalged ning kohati on raske eristada tippe ja servasid. Värvide ja muude parameetrite lisamine käib sisuliselt sama moodi kui ggplot2-s. Sõltuvalt sellest kas parameeter näitab mõne tunnuse väärtusi või on ilu pärast, läheb ta kas funktsiooni aes sisse või jääb sellest välja. Proovime värvi argumendiga.

#Siin anname lihtsalt värvi tippudele
ggraph(g, layout = "fr") +
  geom_node_point(color = "red") +
  geom_edge_link()

#Siin proovime punktide tumedusega infot edasi anda
ggraph(g, layout = "fr") +
  geom_node_point(aes(color = ArtistPopularity)) +
  geom_edge_link()

Paneme tähele, et jooned tõmmatakse punktide peale, sest nende joonistamise käsk tuli pärast tippude oma. Seetõttu on targem järjekorda muuta.

Peamised parameetrid mida muuta on color, shape (punktidel), size (punktidel), width (joontel) ja linetype (joontel).

Graafiliste parameetrite skaleerimiseks on loodud erandi funktsioonid, et näiteks tippude ja servade värve saaks eraldi skaleerida. Tippude puhul töötavad tavalised scale_* funktsioonid aga servadele on näiteks funktsioonid kujul scale_edge_*. Sellegi poolest töötavad need sama moodi kui tavalised ggplot2 variandid.

Eriti vajalik on skaleerimine serva paksuse kontrollimiseks argumendiga width, mille vaikimisi väärtused on suhteliselt jubedad.

Tekstide lisamine graafile

Siiamaani oleme joonistanud küll punktikesi, kuid nende punktide tegelikku identiteeti võime vaid aimata. Et paremini teada saada, mis tippudega on täpsemalt tegu. saame lisada tippudele nimed. Seda saab teha nii funktsiooniga geom_node_text, kui geom_node_label, mis vastavalt joonistavad lihtsalt teksti või siis kastikesega sildi.

Nüüd on aga probleem, et me ei näe enam tippusid , sest need on tekstide taga peidus. Üleüldse on tekste nii palju, et nad ei mahu hästi ekraanile. Kasulik oleks neid kuidagi vähendada. Üks võimalus on näidata ainult mingis mõttes huvitavamaid tekste, näiteks neid artiste kellel on palju jälgijaid. Selleks saame kasutada geom_* argumenti data, milllele me saame anda ette funktsiooni mis modifitseerib selle kihi andmeid.

Pane tähele, et see funktsioon töötab ainult tippude andmetabelil, sest see on see mille geom_node_label ette saab. Et trükkimise vaeva vähendada saab function(.x){.x %>% filter(ArtistFollowers > 10000)} väljendada lühemalt kui ~ .x %>% filter(ArtistFollowers > 10000). Sellist kuju kasutatakse palju ka tidyverse paketis purrr, mis on mõeldud vektorite ja listide töötlemiseks.

ggraph(g, layout = "fr") +
  geom_edge_link(aes(width = maxPopularity), color = "grey60") +
  geom_node_point(aes(size = ArtistFollowers), color = "darkgreen") +
  scale_edge_width(range = c(0, 2)) +
  geom_node_label(aes(label = ArtistName), data = ~ .x %>% filter(ArtistFollowers > 10000))

Siiski on konkreetsed tipud siltide varjus ja kui silte oleks rohkem, siis kattuksid need ka üle. Et proovida leida siltidele head mitte-ülekattuvad positsioonid võib rakendada argumenti repel, mis proovib optimeerida siltide asukohti.

Lõpuks vaatame graafi kujunduselementide muutmist. Siin töötavad tegelikult kõik ggplot2 theme_*funktsioonid aga kuna neil on suur rühk telgede kujutamisel ning kirjeldamisel, siis graafide jaoks nad liiga head ei ole. Parem on kasutada theme_void või theme_graph. Konkreetsete elementide muutmiseks saab kasutada theme funktsiooni.

Ülesanded

  • Proovi saavutada järgmine pilt.

Graafi algoritmid

Graafi struktuurist on võimalik järeldada päris palju huvitavat metainfot, selle info kasutamine võimaldab ka graafe visuaalselt sisukamalt kujutada. Selleks tuleb aga teada kuidas erinevaid graafi algoritme praktikas rakendada.

Kõige lihtsam omadus mille me graaf struktuurist välja saame lugeda on see, kas tipp on ühendatud teiste tippudega või mitte. Selleks on funktsioon node_is_isolated mida saab siis rakendada käskude mutate või filter sees. Pane tähele, et selle käsu töötamiseks peavad olema aktiveeritud tipud. Tippudel on ka palju teisi omadusi mida saab täpsemalt uurida abifailidest ?node_is_isolated.

Järgmisen vaatame tippude seoseid konkreetsete tippudega. Kaugust tippudega saab leida käsuga node_distance_to ja node_distance_from. Suunamata graafi puhul pole suurt vahet kumba kasutada, kuid suunatud graafi puhul tuleb erinevus sisse. Nende käskudega on väga hea eraldada välja alamgraafe, mis keskenduvad ühe punkti ümbrusele. On ka keerukamaid tippude vahelisi suhteid võimalik arvutada ja neid näeb abilehel ?node_distance_to.

Tipu olulisust graafis võib näidata mitut moodi, kuid kõiki neid lähenemisi võetakse kokku ühe terminiga tsentraalsus. Kõig lihtsam tsentraalsuse mõõt, tipu valents, loeb kokku kõik tipu ühendused. Suunatud graafis võib seda teha nii sisse kui väljapoole ühendustega eraldi. R-s saab seda arvutada käsuga centrality_degree. Keerukam tsentraalsusmõõt on betweenness centrality, mis müüdab kui suur osa ühendustest graafi tippude vahel läbib vastavat tippu. Erinevaid algoritme on aga veel (vt ?centrality_degree)

g %>% 
  activate(nodes) %>% 
  mutate(Degree = centrality_degree()) %>% 
  mutate(Centrality = centrality_degree()) %>% 
  ggraph(layout = "kk") + 
    geom_edge_link() + 
    geom_node_point(aes(size = Degree, color = Centrality, alpha = betweenness)) +
    geom_node_label(aes(label = ArtistName), data = ~ .x %>% filter(Centrality > 10))
Error: Aesthetics must be valid data columns. Problematic aesthetic(s): alpha = betweenness. 
Did you mistype the name of a data column or forget to add after_stat()?
Run `rlang::last_error()` to see where the error occurred.

Omadusi võib vaadata ka servadel. Ka nende puhul saab leida servad, mis üheduvad konkreetse tipuga või servasid mis on võrgustikus kesksel kohal. Neid funktsioone saab kasutada käskudega edge_is_from ja centrality_edge_betweenness.

Greefi tippe on ka kasulik klasterdada, et üles leida loogilised alamgraafid. Lihtsaim klasterdamise viis on leida sidusad alamgraafid, mida saab teha käsuga group_components. Keerukamaid kalsterdusi on ka päris palju saadaval ja ühe sellise näiteks võiks olla group_louvain (teiste jaoks uuri ?group_louvain).

g %>% 
  activate(nodes) %>% 
  filter(!node_is_isolated()) %>% 
  mutate(GL = as.factor(group_louvain())) %>% 
  ggraph(layout = "kk") + 
    geom_edge_link() +
    geom_node_point(aes(color = GL))+
  geom_node_label(aes(label = ArtistName), data = ~ .x %>% filter(Centrality > 3))
Error: Problem with `filter()` input `..1`.
x object 'Centrality' not found
ℹ Input `..1` is `Centrality > 3`.
Run `rlang::last_error()` to see where the error occurred.

Ülesanded

  • Visualiseeri antud andmestiku kõige suurem sidus komponent. Näita nimedega kõik artistid, kellel on rohkem kui 3 koostööd. Näita punkti tüübiga ära selle selles graafis olevad klastirid (Louvain-i algoritmi järgi)

Kodune ülesanne

Failis spotify_foreign.RData on samasugused andmed aga välisartistide kohta. Proovi sealt välja tuua mõni sinu arvates huvitav seos ühe visualisatsiooniga. Esitada tuleks nii graafi genereeriv kood kui ka selgitus, mida te pildilt välja lugesite. Pildi puhul hindan, milliseid võtteid te olete kasutanud pildi ettevalmistamisel, kasutatud võiks olla vähemalt üks element kõigest järgnevast.

Lisaks tehnilisele poolele hindan järelduse ja graafiku kokkulangevust ning graafiku visuaalset vormistust.

LS0tCnRpdGxlOiAiUHJha3Rpa3VtIDYgLSBHcmFhZmlkIGphIHbDtXJndXN0aWt1ZCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgU2lzc2VqdWhhdHVzCgpUw6RuYXNlIHByYWt0aWt1bWkgdGVlbWEgb24gZ3JhYWZpZGUgamEgdsO1cmd1c3Rpa2UgdmlzdWFsaXNlZXJpbWluZS4gU2VkYSB0ZWVtZSBtZSBrYXN1dGFkZXMgcGFrZXR0ZSBgdGlkeWdyYXBoYCBqYSBgZ2dyYXBoYC4gTGlzYWtzIGthc3V0YW1hZSBhbmRtZXN0aWtrdSwga3VzIG9uIHbDtWV0dWQgdmlpbWFzZSBwb29sZXRlaXNlIGFhc3RhIHBvcHVsYWFyc2VtYWQgRWVzdGkgbG9vZCBTcG90aWZ5c3QgamEgbmVuZGUgZXNpdGFqYXRlIGtvaHRhIHRla2l0YXR1ZCB2w7VyZ3VzdGlrIHZhc3RhdmFsdCBzZWxsZWxlIGtlcyBvbiBvbWF2YWhlbCBrb29zdMO2w7ZkIHRlaW51ZC4gSWdhIGFydGlzdGkga29odGEgb24gdGVhZGEgdGVtYSBwb3B1bGFhcnN1cyAoc2thYWxhcyAwLTEwMCkgamEgasOkbGdpamF0ZSBhcnYuIElnYSBzZXJ2YSBwdWh1bCBvbiBrb2trdSBsb2V0dWQga29vc3TDtsO2cyB2YWxtaW51ZCBsdWd1ZGUgYXJ2IGphIGvDtWlnZSBwb3B1bGFhcnNlbWEgbG9vIHBvcHVsYWFyc3VzLgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCgpsb2FkKCJzcG90aWZ5X2VzdC5SRGF0YSIsIHZlcmJvc2UgPSBUKQpub2Rlc19lc3QKZWRnZXNfZXN0CmBgYAoKIyMgR3JhYWZpIG9iamVrdAoKIyMjIEdyYWFmaSBvYmpla3RpIGxvb21pbmUgCgpHcmFhZmlkZSBqYW9rcyBkZWZpbmVlcmliIGB0aWR5Z3JhcGhgIHBha2V0dCBvYmpla3RpIGB0YmxfZ3JhcGhgLCBtaXMgb24gc2lzdWxpc2VsdCBrYWtzIHNlb3R1ZCBgdGliYmxlYCB0w7zDvHBpIG9iamVrdGksIMO8a3MgdGlwcHVkZSBqYSB0ZWluZSBzZXJ2YWRlIGphb2tzLiBTZWRhIGRlZmluZWVyaXRha3NlIGvDpHN1Z2EgYHRibF9ncmFwaGAsIG1pbGxlbGUgdHVsZWIgZXR0ZSBhbmRhIGtha3MgYHRpYmJsZWAgdsO1aSBgZGF0YS5mcmFtZWAgdMO8w7xwaSBvYmpla3RpLiBTZWUga8Okc2sgb290YWIsIGV0IHNlcnZhZGUgZmFpbGlzIG9sZWtzIGtha3MgdmVlcmd1OiBgZnJvbWAgamEgYHRvYC4gVGlwdSBuaW1lZCB2ZWVyZ3VkZXMgYGZyb21gIGphIGB0b2AgcGVhdmFkIG9sZW1hIG9sZW1hcyBrYSB0aXBwdWRlIHRhYmVsaXMgbWluZ2kgdHVubnVzZW5hLCBtaWxsZSBuaW1pIGFudGFrc2UgZXR0ZSBhcmd1bWVuZGlnYSBgbm9kZV9rZXlgLiBUdWxla3Mga2EgbcOkw6RyYXRhLCBrYXMgZ3JhYWYgb24gc3V1bmF0dWQgdsO1aSBzdXVuYW1hdGEsIGFyZ3VtZW5kaWdhIGBkaXJlY3RlZGAuCgpgYGB7cn0KZWRnZXNfZXN0ID0gZWRnZXNfZXN0ICU+JSAKICByZW5hbWUoZnJvbSA9IEFydGlzdE5hbWUxLCB0byA9IEFydGlzdE5hbWUyKQoKZyA9IHRibF9ncmFwaChub2RlcyA9IG5vZGVzX2VzdCwgZWRnZXMgPSBlZGdlc19lc3QsIG5vZGVfa2V5ID0gIkFydGlzdE5hbWUiLCBkaXJlY3RlZCA9IEYpCmcgCmBgYAoKR3JhYWZlIHNhYWIgbHV1YSBrYSBrYXN1dGFkZXMgbmFhYnJ1c21hYXRyaWtzZWlkLCBrYXN1dGFkZXMgcGFrZXRpIGBpZ3JhcGhgIGZ1a3RzaW9vbmkgYGdyYXBoLmFkamFjZW5jeWAgamEgbXV1dGVzIHNlbGxlIGB0YmxfZ3JhcGhgIG9iamVrdGlrcyBmdW5rdHNpb29uaWdhIGBhc190YmxfZ3JhcGhgLgoKYGBge3J9CmxpYnJhcnkoaWdyYXBoKQoKbWF0ID0gY2JpbmQoYygwLCAwLCAxKSwgYygxLCAwLCAxKSwgYygxLCAwLCAwKSkKcm93bmFtZXMobWF0KSA9IGMoIkEiLCAiQiIsICJDIikKY29sbmFtZXMobWF0KSA9IGMoIkEiLCAiQiIsICJDIikKbWF0CgpncmFwaC5hZGphY2VuY3kobWF0KSAlPiUgCiAgYXNfdGJsX2dyYXBoKCkKYGBgCgojIyMgR3JhYWZpZGUgbWFuaXB1bGVlcmltaW5lCgpHcmFhZmkgb2JqZWt0aSBtYW5pcHVsZWVyaW1pbmUgdGlkeWdyYXBoIHBha2V0aXMgb24gdsOkZ2Egc2FybmFuZSB0YXZhbGlzdGUgYW5kbWV0YWJlbGl0ZSB0w7bDtnRsZW1pc2VnYSBgdGlkeXZlcnNlYCBhYmlsLiBLdW5hIGFnYSBncmFhZmkgb2JqZWt0IGtvb3NuZWIga2FoZXN0IGFuZG1ldGFiZWxpc3QsIHNpaXMgb24gbGlzYXR1ZCBrw6RzayBuaW1lZ2EgYGFjdGl2YXRlYCwgbWlsbGVnYSBzYWFiIGV0dGUgw7ZlbGRhIGdyYWFmLCBrYXMgbWUgcGxhYW5pbWUgcmFrZW5kYWRhIGvDpHNrZSB0aXBwdWRlIHbDtWkgc2VydmFkZSB0YWJlbGlsZS4gVmFzdGF2YWx0IHNpaXMgYGFjdGl2YXRlKG5vZGVzKWAgamEgYGFjdGl2YXRlKGVkZ2VzKWAgYWt0aXZlZXJpdmFkIHZhc3RhdmFsdCB0aXB1ZCBqYSBzZXJ2YWQuIFBlYWxlIHNlZGEgc2FhbWUgcmFrZW5kYWRhIHR1dHRhdmFpZCBrw6Rza2UgbmFndSBuw6RpdGVrcyBgbXV0YXRlYC4KCmBgYHtyfQpnICU+JSAKICBhY3RpdmF0ZShlZGdlcykgJT4lIAogIG11dGF0ZShWZXJ5UG9wdWxhciA9IG1heFBvcHVsYXJpdHkgPiA0MCkKCmcgJT4lIAogIGFjdGl2YXRlKG5vZGVzKSAlPiUgCiAgbXV0YXRlKFZlcnlQb3B1bGFyID0gQXJ0aXN0UG9wdWxhcml0eSA+IDQ1KQpgYGAKCkt1aSBmaWx0cmVlcmlkYSB0aXBwdXNpZCwgc2lpcyBvbiBzZWxsZWwgdGFnYWrDpHJqZWQga2Egc2VydmFkZWxlLiBOaW1lbHQga3VzdHV0YXRha3NlIMOkcmEga2Egc2VydmFkLCBtaWxsZSBwdWh1bCB2w6RoZW1hbHQgw7xrcyB0aXBwIGFuZGVtdGVzdCB2w6RsamEgdmlzYXRha3NlLiBTZWUgb24gbXVnYXYsIHNlc3QgbmlpIGVpIHBlYSBtdXJldHNlbWEsIGV0IGtha3MgdGFiZWxpdCBzw7xua3Jvb25pc3QgdsOkbGphIGzDpGhldmFkLgoKYGBge3J9CmcgJT4lIAogIGFjdGl2YXRlKG5vZGVzKSAlPiUgCiAgbXV0YXRlKFZlcnlQb3B1bGFyID0gQXJ0aXN0UG9wdWxhcml0eSA+IDQ1KSAlPiUKICBmaWx0ZXIoVmVyeVBvcHVsYXIgPT0gVCkKYGBgCgpFcmluZXZhdGUgYHRpZHl2ZXJzZWAgZnVua3RzaW9vbmlkZSBrYXN1dGFtaXNlbCBvbiBsb29tdWxpa3VsdCBwaWlyaWQuIE7DpGl0ZWtzIHN1bW1hcml6ZSBvbiBrw6RzayBtaWxsZSBrw6RpdHVtaXN0IGdyYWFmaWwgZWkgb2xlIHbDtWltYWxpayDDvGtzw7xoZWxlIMO8bGUga2FuZGEgamEgc2VlZ2Egc2VlIGFudHVkIGp1aHVsIGVpIHTDtsO2dGEuIEvDvGxsIGFnYSB0w7bDtnRhdmFkIGvDtWlrIGAqX2pvaW5gIGZ1bmt0c2lvb25pZCBqYSBvbiBkZWZpbmVlcml0dWQgZ3JhYWZpZGUgbGlpdG1pbmUgYGJpbmRfZ3JhcGhgIGFiaWwuIExvZSBsw6RoZW1hbHQgbGluZ2lsdCA8aHR0cHM6Ly93d3cuZGF0YS1pbWFnaW5pc3QuY29tLzIwMTcvaW50cm9kdWNpbmctdGlkeWdyYXBoLz4uCgojIyMjIMOcbGVzYW5kZWQKCkxvb21lIGtha3MgdGFiZWxpdAoKYGBge3J9Cm4gPSB0aWJibGUoCiAgTmFtZSA9IGMoIkEiLCAiQiIsICJDIiwgIkQiKSwKICBWYWx1ZSA9IDE6NCwKICBDbGFzcyA9IGMoIkNsYXNzMSIsICJDbGFzczIiLCAiQ2xhc3MyIiwgIkNsYXNzMiIpCikKCmUgPSB0aWJibGUoCiAgTmFtZTEgPSBjKCJBIiwgIkEiLCAiQiIsICJDIiwgIkMiLCAiRCIpLAogIE5hbWUyID0gYygiQyIsICJEIiwgIkEiLCAiQSIsICJCIiwgIkIiKQopCmBgYAoKYGBge3J9CmUgPC0gZSAlPiUgCiAgcmVuYW1lKGZyb20gPSBOYW1lMSwgdG8gPSBOYW1lMikKCnRibF9ncmFwaCRub2Rlcyhub2RlcyA9ZSwgbm9kZV9rZXkgPSApCmBgYAoKCi0gICBOZW5kZSB0YWJlbGl0ZSBww7VoamFsIGxvb2dlIGB0YmxfZ3JhcGhgIHTDvMO8cGkgb2JqZWt0LiBNw7VlbGdlLCBrYXMgYW50dWQgZ3JhYWYgcGVha3Mgb2xlbWEgc3V1bmF0dWQgdsO1aSBzdXVuYW1hdGEKCi0gICBTYWFkdWQgZ3JhYWZpc3QgZmlsdHJlZXJpZ2UgdsOkbGphIGFsYW1ncmFhZiBtaXMgc2lzYWxkYWIgYWludWx0IGtsYXNzaSAiQ2xhc3MyIiBrdXVsdXZhaWQgdGlwcGUuCgojIyBHcmFhZmkgam9vbmlzdGFtaW5lIAoKR3JhYWZlIGpvb25pc3RhbWUgc2VsbGVzIHByYWt0aWt1bWlzIHBha2V0aWdhIGBnZ3JhcGhgLCBtaXMgb24gc2lzbGlzZWx0IGxhaWVuZHVzIGBnZ3Bsb3QyYC1sZSBncmFhZmkgYW5kbWV0ZSBqYW9rcy4gR3JhYWZpayBsdXVrYXNlIHNpaW4ga8Okc3VnYSBgZ2dyYXBoYCBtaWxsZWxlIGhha2F0YWtzZSBzaWlzIGdyYWFmaWt1IGVsZW1lbnRlIGxpaXRtYS4gVGlwcHVkZSBqb29uaXN0YW1pc2VrcyBvbiBmdW5rdHNpb29uaWQgYGdlb21fbm9kZV8qYCBqYSBzZXJ2YWRlIGphb2tzIGBnZW9tX2VkZ2VfKmAuIEFuZG1lcHVua3RpZGUga3VqdXR1c2VkIGdyYWFmaWxpc3RlbGUgcGFyYW1lZXRyaXRlbGUgYW50ZWtzZSBldHRlIGBnZW9tX2AgZnVua3RzaW9vbmlkZSBzZWVzLCBzZXN0IG1lIHbDtWltZSB0YWh0YSBzYW1hIHBhcmFtZWV0cml0IChuw6RpdC4gdsOkcnYpIGRlZmluZWVyaWRhIG5paSB0aXBwdWRlbGUga3VpIHNlcnZhZGVsZS4KCmBgYHtyfQpnZ3JhcGgoZykgKwogIGdlb21fbm9kZV9wb2ludCgpICsKICBnZW9tX2VkZ2VfbGluaygpCmBgYAoKQW50dWQgZ3JhYWYgZWkgbsOkZSB2w6RnYSBoZWEgdsOkbGphLCBzZXN0IGF1dG9tYWF0c2VsdCB2YWxpdHVkIHB1bmt0aWRlIHBhaWd1dHVzZSBhbGdvcml0bSAoc3RyZXNzKSwgZWkgb2xlIG9wdGltYWFsbmUuIFB1bmt0aWRlIHBhaWd1dHVzdCB2w7VpYiBrb250cm9sbGlkYSBwYXJhbWVldHJpZ2EgYGxheW91dGAuCgpFc2l0ZWtzIHbDtWliIHNlbGxlbGUgZXR0ZSBhbmRhIMO8aGUgbWl0bWVzdCBwYWlndXR1cyBhbGdvcml0bWlzdCwgbWlsbGVzdCBtw7VuZWQgb24gasOkcmduZXZhZDoKCi0gICAia2siIC0gS2FtYWRhIGFuZCBLYXdhaSBqw7V1ZHVkZWwgcMO1aGluZXYgYWxnb3JpdG0KCi0gICAiZnIiIC0gRnJ1Y2h0ZXJtYW4gYW5kIFJlaW5nb2xkIGrDtXVkdWRlbCBww7VoaW5ldiBhbGdvcml0bQoKLSAgICJkcmwiIC0gasOkcmpla29yZG5lIGrDtXVkdWRlbCBww7VoaW5ldiBhbGdvcml0bQoKLSAgICJnZW0iIC0gasOkcmpla29yZG5lIGrDtXVkdWRlbCBww7VoaW5ldiBhbGdvcml0bQoKLSAgICJzdHJlc3MiIC0gasOkcmpla29yZG5lIGrDtXVkdWRlbCBww7VoaW5ldiBhbGdvcml0bQoKLSAgICJsZ2wiIC0gc3V1cnRlbGUgZ3JhYWZpZGVsZSBzb2JpdiBhbGdvcml0bQoKLSAgICJtZHMiIC0ga2F1Z3VzdGUgbWFhdHJpa2lsIGRpbWVuc2lpbmkgdsOkaGVuZGFtaWVnYSBwYWlndXRhbWluZQoKLSAgICJkaCIgLSBzdG9oaGFzdGlsaXNlbCBsb2thYWxzZWwgb3RzaW5ndWwgcMO1aGluZXYgYWxnb3JpdG0KCi0gICAibmljZWx5IiAtIHByb292aWIgdmFsaWRhIGFuZG1ldGVsZSB2YXN0YXZhbCBoZWEgYWxnb3JpdG1pCgotICAgImNpcmNsZSIgLSByaW5naSBrdWp1bGluZSBwYWlndXR1cwoKYGBge3J9CmdncmFwaChnLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fZWRnZV9saW5rKCkKYGBgCgpQYWlndXR1c2UgdsO1aWIgaGVhIHRhaHRtaXNlIGtvcnJhbCBrYSBpc2UgZXR0ZSBhbmRhIG1hYXRyaWtzaW5hLgoKYGBge3J9CmcgIAoKI2xvb21lIGlzZSBqdWh1c2xpa3UgcGFpZ3V0dXNlLiBFaSBvbGUgaGVhCmxvID0gY2JpbmQocnVuaWYoODcpLCBydW5pZig4NykpCgpsbwoKZ2dyYXBoKGcsIGxheW91dCA9IGxvKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fZWRnZV9saW5rKCkKYGBgCgpUaXBwdWRlIG7DpGl0YW1pc2VsLCBwZWFsZSBwdW5rdGlkZSB2w6RnYSBow6RpZCB2w7VpbWFsdXNpIHBvbGUuIEvDvGxsIGFnYSBvbiBlcmluZXZhaWQgdsO1aW1hbHVzaSBzZXJ2YWRlIGpvb25pc3RhbWlzZWtzLiBLw7VpZ2Uga2FzdXRhdGF2YW0gb24ganViYSBlZWxwb29sIGthc3V0YXR1ZCBgZ2VvbV9lZGdlX2xpbmtgLCBtaXMgdMO1bWJhYiB0aXBwdWRlIHZhaGVsZSBzaXJnZWQgam9vbmVkLiBUZWlzZWQgdsO1aW1hbHVzZWQgb24gdmVlbDoKCi0gICBgZ2VvbV9lZGdlX2FyY2AgLSB0ZWViIGthYXJlIHB1bmt0aWRlIHZhaGVsZQoKLSAgIGBnZW9tX2VkZ2VfZGVuc2l0eWAgLSBqb29uZSBhc2VtZWwgdGVlYiBrYWhlIHB1bmt0aSB2YWhlbGlzdCBhbGEgbmF0dWtlIHR1bWVkYW1ha3MsIGthc3VsaWssIGt1aSBncmFhZiBvbiB2w6RnYSBzdXVyIGphIHRpaGUKCmBgYHtyfQpnZ3JhcGgoZywgbGF5b3V0ID0gImxpbmVhciIpICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgZ2VvbV9lZGdlX2FyYygpCgpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fbm9kZV9wb2ludCgpICsKICBnZW9tX2VkZ2VfZGVuc2l0eSgpCmBgYAoKS3VpIGdyYWFmaWQgb24gc3V1bmF0dWQgc2FhYiBqb29udGVsZSBsaXNhZGEga2Egbm9vbGksIG1pZGEgc2FhYiBzcGV0c2lmaXRzZWVyaWRhIGFyZ3VtZW5kaWdhIGBhcnJvd2AsIG1pcyBvbWFta29yZGEgdsO1dGFiIHbDpMOkcnR1c2VrcyBgYXJyb3dgIGZ1bmt0c2lvb25pIGBncmlkYCBwYWtldGlzdC4KCmBgYHtyfQpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fbm9kZV9wb2ludCgpICsKICBnZW9tX2VkZ2VfbGluayhhcnJvdyA9IGdyaWQ6OmFycm93KGxlbmd0aCA9IHVuaXQoMC4xLCAiaW5jaGVzIiksIHR5cGUgPSAiY2xvc2VkIikpICNjbG9zZWQgamEgb3BlbiB0w6RoZW5kYXZhZCBub29sZSB0w7zDvHBpCmBgYAoKIyMjIEdyYWFmaWwgbWV0YS1hbmRtZXRlIG7DpGl0YW1pbmUKClNpaWFuaSBuw6RpZGF0dWQgZ3JhYWZpa3VkIG9uIG9sbnVkIHbDpGdhIG11c3R2YWxnZWQgbmluZyBrb2hhdGkgb24gcmFza2UgZXJpc3RhZGEgdGlwcGUgamEgc2VydmFzaWQuIFbDpHJ2aWRlIGphIG11dWRlIHBhcmFtZWV0cml0ZSBsaXNhbWluZSBrw6RpYiBzaXN1bGlzZWx0IHNhbWEgbW9vZGkga3VpIGdncGxvdDItcy4gU8O1bHR1dmFsdCBzZWxsZXN0IGthcyBwYXJhbWVldGVyIG7DpGl0YWIgbcO1bmUgdHVubnVzZSB2w6TDpHJ0dXNpIHbDtWkgb24gaWx1IHDDpHJhc3QsIGzDpGhlYiB0YSBrYXMgZnVua3RzaW9vbmkgYWVzIHNpc3NlIHbDtWkgasOkw6RiIHNlbGxlc3QgdsOkbGphLiBQcm9vdmltZSB2w6RydmkgYXJndW1lbmRpZ2EuCgpgYGB7cn0KI1NpaW4gYW5uYW1lIGxpaHRzYWx0IHbDpHJ2aSB0aXBwdWRlbGUKZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX25vZGVfcG9pbnQoY29sb3IgPSAicmVkIikgKwogIGdlb21fZWRnZV9saW5rKCkKCiNTaWluIHByb292aW1lIHB1bmt0aWRlIHR1bWVkdXNlZ2EgaW5mb3QgZWRhc2kgYW5kYQpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3IgPSBBcnRpc3RQb3B1bGFyaXR5KSkgKwogIGdlb21fZWRnZV9saW5rKCkKYGBgCgpQYW5lbWUgdMOkaGVsZSwgZXQgam9vbmVkIHTDtW1tYXRha3NlIHB1bmt0aWRlIHBlYWxlLCBzZXN0IG5lbmRlIGpvb25pc3RhbWlzZSBrw6RzayB0dWxpIHDDpHJhc3QgdGlwcHVkZSBvbWEuIFNlZXTDtXR0dSBvbiB0YXJnZW0gasOkcmpla29yZGEgbXV1dGEuCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluaygpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yID0gQXJ0aXN0UG9wdWxhcml0eSkpIApgYGAKClBlYW1pc2VkIHBhcmFtZWV0cmlkIG1pZGEgbXV1dGEgb24gYGNvbG9yYCwgYHNoYXBlYCAocHVua3RpZGVsKSwgYHNpemVgIChwdW5rdGlkZWwpLCBgd2lkdGhgIChqb29udGVsKSBqYSBgbGluZXR5cGVgIChqb29udGVsKS4KCmBgYHtyfQpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhjb2xvciA9IG5UcmFja3MpKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gQXJ0aXN0Rm9sbG93ZXJzKSwgY29sb3IgPSAiZGFya2dyZWVuIikgCmBgYAoKR3JhYWZpbGlzdGUgcGFyYW1lZXRyaXRlIHNrYWxlZXJpbWlzZWtzIG9uIGxvb2R1ZCBlcmFuZGkgZnVua3RzaW9vbmlkLCBldCBuw6RpdGVrcyB0aXBwdWRlIGphIHNlcnZhZGUgdsOkcnZlIHNhYWtzIGVyYWxkaSBza2FsZWVyaWRhLiBUaXBwdWRlIHB1aHVsIHTDtsO2dGF2YWQgdGF2YWxpc2VkIGBzY2FsZV8qYCBmdW5rdHNpb29uaWQgYWdhIHNlcnZhZGVsZSBvbiBuw6RpdGVrcyBmdW5rdHNpb29uaWQga3VqdWwgYHNjYWxlX2VkZ2VfKmAuIFNlbGxlZ2kgcG9vbGVzdCB0w7bDtnRhdmFkIG5lZWQgc2FtYSBtb29kaSBrdWkgdGF2YWxpc2VkIGBnZ3Bsb3QyYCB2YXJpYW5kaWQuCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMoY29sb3IgPSBuVHJhY2tzKSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IEFydGlzdEZvbGxvd2VycyksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICBzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJwaW5rIiwgaGlnaCA9ICJyZWQiKQpgYGAKCkVyaXRpIHZhamFsaWsgb24gc2thbGVlcmltaW5lIHNlcnZhIHBha3N1c2Uga29udHJvbGxpbWlzZWtzIGFyZ3VtZW5kaWdhIHdpZHRoLCBtaWxsZSB2YWlraW1pc2kgdsOkw6RydHVzZWQgb24gc3VodGVsaXNlbHQganViZWRhZC4KCmBgYHtyfQpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhjb2xvciA9IG5UcmFja3MsIHdpZHRoID0gbWF4UG9wdWxhcml0eSkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBBcnRpc3RGb2xsb3dlcnMpLCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgc2NhbGVfZWRnZV9jb2xvcl9ncmFkaWVudChsb3cgPSAicGluayIsIGhpZ2ggPSAicmVkIikgCgpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhjb2xvciA9IG5UcmFja3MsIHdpZHRoID0gbWF4UG9wdWxhcml0eSkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBBcnRpc3RGb2xsb3dlcnMpLCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgc2NhbGVfZWRnZV9jb2xvcl9ncmFkaWVudChsb3cgPSAicGluayIsIGhpZ2ggPSAicmVkIikgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAsIDIpKQpgYGAKCiMjIyBUZWtzdGlkZSBsaXNhbWluZSBncmFhZmlsZQoKU2lpYW1hYW5pIG9sZW1lIGpvb25pc3RhbnVkIGvDvGxsIHB1bmt0aWtlc2ksIGt1aWQgbmVuZGUgcHVua3RpZGUgdGVnZWxpa2t1IGlkZW50aXRlZXRpIHbDtWltZSB2YWlkIGFpbWF0YS4gRXQgcGFyZW1pbmkgdGVhZGEgc2FhZGEsIG1pcyB0aXBwdWRlZ2Egb24gdMOkcHNlbWFsdCB0ZWd1LiBzYWFtZSBsaXNhZGEgdGlwcHVkZWxlIG5pbWVkLiBTZWRhIHNhYWIgdGVoYSBuaWkgZnVua3RzaW9vbmlnYSBgZ2VvbV9ub2RlX3RleHRgLCBrdWkgYGdlb21fbm9kZV9sYWJlbGAsIG1pcyB2YXN0YXZhbHQgam9vbmlzdGF2YWQgbGlodHNhbHQgdGVrc3RpIHbDtWkgc2lpcyBrYXN0aWtlc2VnYSBzaWxkaS4KCmBgYHtyfQpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IG1heFBvcHVsYXJpdHkpLCBjb2xvciA9ICJncmV5NjAiKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gQXJ0aXN0Rm9sbG93ZXJzKSwgY29sb3IgPSAiZGFya2dyZWVuIikgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAsIDIpKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gQXJ0aXN0TmFtZSkpCgpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IG1heFBvcHVsYXJpdHkpLCBjb2xvciA9ICJncmV5NjAiKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gQXJ0aXN0Rm9sbG93ZXJzKSwgY29sb3IgPSAiZGFya2dyZWVuIikgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAsIDIpKSArCiAgZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IEFydGlzdE5hbWUpKQpgYGAKCk7DvMO8ZCBvbiBhZ2EgcHJvYmxlZW0sIGV0IG1lIGVpIG7DpGUgZW5hbSB0aXBwdXNpZCAsIHNlc3QgbmVlZCBvbiB0ZWtzdGlkZSB0YWdhIHBlaWR1cy4gw5xsZcO8bGRzZSBvbiB0ZWtzdGUgbmlpIHBhbGp1LCBldCBuYWQgZWkgbWFodSBow6RzdGkgZWtyYWFuaWxlLiBLYXN1bGlrIG9sZWtzIG5laWQga3VpZGFnaSB2w6RoZW5kYWRhLiDDnGtzIHbDtWltYWx1cyBvbiBuw6RpZGF0YSBhaW51bHQgbWluZ2lzIG3DtXR0ZXMgaHV2aXRhdmFtYWlkIHRla3N0ZSwgbsOkaXRla3MgbmVpZCBhcnRpc3RlIGtlbGxlbCBvbiBwYWxqdSBqw6RsZ2lqYWlkLiBTZWxsZWtzIHNhYW1lIGthc3V0YWRhIGBnZW9tXypgIGFyZ3VtZW50aSBgZGF0YWAsIG1pbGxsZWxlIG1lIHNhYW1lIGFuZGEgZXR0ZSBmdW5rdHNpb29uaSBtaXMgbW9kaWZpdHNlZXJpYiBzZWxsZSBraWhpIGFuZG1laWQuCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBtYXhQb3B1bGFyaXR5KSwgY29sb3IgPSAiZ3JleTYwIikgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IEFydGlzdEZvbGxvd2VycyksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLCAyKSkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3ROYW1lKSwgZGF0YSA9IGZ1bmN0aW9uKC54KXsueCAlPiUgZmlsdGVyKEFydGlzdEZvbGxvd2VycyA+IDEwMDAwKX0pCmBgYAoKUGFuZSB0w6RoZWxlLCBldCBzZWUgZnVua3RzaW9vbiB0w7bDtnRhYiBhaW51bHQgdGlwcHVkZSBhbmRtZXRhYmVsaWwsIHNlc3Qgc2VlIG9uIHNlZSBtaWxsZSBgZ2VvbV9ub2RlX2xhYmVsYCBldHRlIHNhYWIuIEV0IHRyw7xra2ltaXNlIHZhZXZhIHbDpGhlbmRhZGEgc2FhYiBgZnVuY3Rpb24oLngpey54ICU+JSBmaWx0ZXIoQXJ0aXN0Rm9sbG93ZXJzID4gMTAwMDApfWAgdsOkbGplbmRhZGEgbMO8aGVtYWx0IGt1aSBgfiAueCAlPiUgZmlsdGVyKEFydGlzdEZvbGxvd2VycyA+IDEwMDAwKWAuIFNlbGxpc3Qga3VqdSBrYXN1dGF0YWtzZSBwYWxqdSBrYSB0aWR5dmVyc2UgcGFrZXRpcyBgcHVycnJgLCBtaXMgb24gbcO1ZWxkdWQgdmVrdG9yaXRlIGphIGxpc3RpZGUgdMO2w7Z0bGVtaXNla3MuCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBtYXhQb3B1bGFyaXR5KSwgY29sb3IgPSAiZ3JleTYwIikgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IEFydGlzdEZvbGxvd2VycyksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLCAyKSkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3ROYW1lKSwgZGF0YSA9IH4gLnggJT4lIGZpbHRlcihBcnRpc3RGb2xsb3dlcnMgPiAxMDAwMCkpCmBgYAoKU2lpc2tpIG9uIGtvbmtyZWV0c2VkIHRpcHVkIHNpbHRpZGUgdmFyanVzIGphIGt1aSBzaWx0ZSBvbGVrcyByb2hrZW0sIHNpaXMga2F0dHVrc2lkIG5lZWQga2Egw7xsZS4gRXQgcHJvb3ZpZGEgbGVpZGEgc2lsdGlkZWxlIGhlYWQgbWl0dGUtw7xsZWthdHR1dmFkIHBvc2l0c2lvb25pZCB2w7VpYiByYWtlbmRhZGEgYXJndW1lbnRpIHJlcGVsLCBtaXMgcHJvb3ZpYiBvcHRpbWVlcmlkYSBzaWx0aWRlIGFzdWtvaHRpLgoKYGBge3J9CmdncmFwaChnLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gbWF4UG9wdWxhcml0eSksIGNvbG9yID0gImdyZXk2MCIpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBBcnRpc3RGb2xsb3dlcnMpLCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMCwgMikpICsKICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gQXJ0aXN0TmFtZSksIGRhdGEgPSB+IC54ICU+JSBmaWx0ZXIoQXJ0aXN0Rm9sbG93ZXJzID4gNTAwMCkpCgpnZ3JhcGgoZywgbGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IG1heFBvcHVsYXJpdHkpLCBjb2xvciA9ICJncmV5NjAiKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gQXJ0aXN0Rm9sbG93ZXJzKSwgY29sb3IgPSAiZGFya2dyZWVuIikgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAsIDIpKSArCiAgZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IEFydGlzdE5hbWUpLCByZXBlbCA9IFQsIGRhdGEgPSB+IC54ICU+JSBmaWx0ZXIoQXJ0aXN0Rm9sbG93ZXJzID4gNTAwMCkpCmBgYAoKTMO1cHVrcyB2YWF0YW1lIGdyYWFmaSBrdWp1bmR1c2VsZW1lbnRpZGUgbXV1dG1pc3QuIFNpaW4gdMO2w7Z0YXZhZCB0ZWdlbGlrdWx0IGvDtWlrIGBnZ3Bsb3QyYCBgdGhlbWVfKmBmdW5rdHNpb29uaWQgYWdhIGt1bmEgbmVpbCBvbiBzdXVyIHLDvGhrIHRlbGdlZGUga3VqdXRhbWlzZWwgbmluZyBraXJqZWxkYW1pc2VsLCBzaWlzIGdyYWFmaWRlIGphb2tzIG5hZCBsaWlnYSBoZWFkIGVpIG9sZS4gUGFyZW0gb24ga2FzdXRhZGEgYHRoZW1lX3ZvaWRgIHbDtWkgYHRoZW1lX2dyYXBoYC4gS29ua3JlZXRzZXRlIGVsZW1lbnRpZGUgbXV1dG1pc2VrcyBzYWFiIGthc3V0YWRhIGB0aGVtZWAgZnVua3RzaW9vbmkuCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBtYXhQb3B1bGFyaXR5KSwgY29sb3IgPSAiZ3JleTYwIikgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IEFydGlzdEZvbGxvd2VycyksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLCAyKSkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3ROYW1lKSwgcmVwZWwgPSBULCBkYXRhID0gfiAueCAlPiUgZmlsdGVyKEFydGlzdEZvbGxvd2VycyA+IDUwMDApKSArCiAgdGhlbWVfYncoKQoKZ2dyYXBoKGcsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBtYXhQb3B1bGFyaXR5KSwgY29sb3IgPSAiZ3JleTYwIikgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IEFydGlzdEZvbGxvd2VycyksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLCAyKSkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3ROYW1lKSwgcmVwZWwgPSBULCBkYXRhID0gfiAueCAlPiUgZmlsdGVyKEFydGlzdEZvbGxvd2VycyA+IDUwMDApKSArCiAgdGhlbWVfdm9pZCgpCmBgYAoKIyMjIyAgw5xsZXNhbmRlZAoKLSAgIFByb292aSBzYWF2dXRhZGEgasOkcmdtaW5lIHBpbHQuCgohW10oaW1hZ2VzL1NjcmVlbnNob3QlMjAyMDIxLTAzLTE0JTIwYXQlMjAyMy41My4yNi5wbmcpCgpgYGB7cn0KZ2dyYXBoKGcsIGxheW91dCA9ICJjaXJjbGUiKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbG9yID0gbWF4UG9wdWxhcml0eSkpKwogIHNjYWxlX2VkZ2VfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGMobG93ID0gIndoaXRlIiwgbWlkPSJyZWQiLCBoaWdoID0gImRhcmtibHVlIikpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yPUFydGlzdEdlbnJlKSkgKwogIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3RHZW5yZSksIGRhdGEgPSBmdW5jdGlvbigueCl7LnggJT4lIGZpbHRlcihBcnRpc3RHZW5yZSA9PSAiZXN0b25pYW4gaW5kaWUiKX0pCiAgCmBgYAoKCiMjIEdyYWFmaSBhbGdvcml0bWlkCgpHcmFhZmkgc3RydWt0dXVyaXN0IG9uIHbDtWltYWxpayBqw6RyZWxkYWRhIHDDpHJpcyBwYWxqdSBodXZpdGF2YXQgbWV0YWluZm90LCBzZWxsZSBpbmZvIGthc3V0YW1pbmUgdsO1aW1hbGRhYiBrYSBncmFhZmUgdmlzdWFhbHNlbHQgc2lzdWthbWFsdCBrdWp1dGFkYS4gU2VsbGVrcyB0dWxlYiBhZ2EgdGVhZGEga3VpZGFzIGVyaW5ldmFpZCBncmFhZmkgYWxnb3JpdG1lIHByYWt0aWthcyByYWtlbmRhZGEuCgpLw7VpZ2UgbGlodHNhbSBvbWFkdXMgbWlsbGUgbWUgZ3JhYWYgc3RydWt0dXVyaXN0IHbDpGxqYSBzYWFtZSBsdWdlZGEgb24gc2VlLCBrYXMgdGlwcCBvbiDDvGhlbmRhdHVkIHRlaXN0ZSB0aXBwdWRlZ2EgdsO1aSBtaXR0ZS4gU2VsbGVrcyBvbiBmdW5rdHNpb29uIGBub2RlX2lzX2lzb2xhdGVkYCBtaWRhIHNhYWIgc2lpcyByYWtlbmRhZGEga8Okc2t1ZGUgYG11dGF0ZWAgdsO1aSBgZmlsdGVyYCBzZWVzLiBQYW5lIHTDpGhlbGUsIGV0IHNlbGxlIGvDpHN1IHTDtsO2dGFtaXNla3MgcGVhdmFkIG9sZW1hIGFrdGl2ZWVyaXR1ZCB0aXB1ZC4gVGlwcHVkZWwgb24ga2EgcGFsanUgdGVpc2kgb21hZHVzaSBtaWRhIHNhYWIgdMOkcHNlbWFsdCB1dXJpZGEgYWJpZmFpbGlkZXN0IGA/bm9kZV9pc19pc29sYXRlZGAuCgpgYGB7cn0KI1NlbGxla3MsIGV0IHBhcmVtaW5pIG9sZWtzIG7DpGhhIMO8aGVuZGF0dWQgdGlwdWQKZyAlPiUgCiAgYWN0aXZhdGUobm9kZXMpICU+JSAKICBtdXRhdGUoSXNvbGF0ZWQgPSBub2RlX2lzX2lzb2xhdGVkKCkpICU+JSAKICBnZ3JhcGgobGF5b3V0ID0gImtrIikgKwogICAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBJc29sYXRlZCksIGNvbG9yID0gImRhcmtncmVlbiIpICsKICAgIHNjYWxlX3NpemVfZGlzY3JldGUocmFuZ2UgPSBjKDMsIDEpKQoKI1ZhYXRhbWUgYWludWx0IG5laWQgdGlwcGUsIG1pcyBlaSBvbGUgaXNvbGVlcml0dWQgKGlzb2xlZXJpdHVkIG1laWQgZWkgaHV2aXRhIG5hZ3VuaWkpCmcgJT4lIAogIGFjdGl2YXRlKG5vZGVzKSAlPiUgCiAgZmlsdGVyKCFub2RlX2lzX2lzb2xhdGVkKCkpICU+JSAKICBnZ3JhcGgobGF5b3V0ID0gImtrIikgKwogICAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgICBnZW9tX25vZGVfcG9pbnQoY29sb3IgPSAiZGFya2dyZWVuIikKYGBgCgpKw6RyZ21pc2VuIHZhYXRhbWUgdGlwcHVkZSBzZW9zZWlkIGtvbmtyZWV0c2V0ZSB0aXBwdWRlZ2EuIEthdWd1c3QgdGlwcHVkZWdhIHNhYWIgbGVpZGEga8Okc3VnYSBgbm9kZV9kaXN0YW5jZV90b2AgamEgYG5vZGVfZGlzdGFuY2VfZnJvbWAuIFN1dW5hbWF0YSBncmFhZmkgcHVodWwgcG9sZSBzdXVydCB2YWhldCBrdW1iYSBrYXN1dGFkYSwga3VpZCBzdXVuYXR1ZCBncmFhZmkgcHVodWwgdHVsZWIgZXJpbmV2dXMgc2lzc2UuIE5lbmRlIGvDpHNrdWRlZ2Egb24gdsOkZ2EgaGVhIGVyYWxkYWRhIHbDpGxqYSBhbGFtZ3JhYWZlLCBtaXMga2Vza2VuZHV2YWQgw7xoZSBwdW5rdGkgw7xtYnJ1c2VsZS4gT24ga2Ega2VlcnVrYW1haWQgdGlwcHVkZSB2YWhlbGlzaSBzdWh0ZWlkIHbDtWltYWxpayBhcnZ1dGFkYSBqYSBuZWlkIG7DpGViIGFiaWxlaGVsIGA/bm9kZV9kaXN0YW5jZV90b2AuCgpgYGB7cn0KI1ZhYXRhbWUgTnVibHUga2F1Z3VzZWlkIGphIGZpbHRyZWVyaW1lIHbDpGxqYSBuZWVkLCBrZXMgb24gTnVibHVzdCAyIHbDtWkgdsOkaGVtYSBzYW1tdSBrYXVndXNlbApnICU+JSAKICBhY3RpdmF0ZShub2RlcykgJT4lIAogIG11dGF0ZShkTnVibHUgPSBub2RlX2Rpc3RhbmNlX3RvKEFydGlzdE5hbWUgPT0gIk51Ymx1IikpICU+JSAKICBmaWx0ZXIoZE51Ymx1IDwgMykgJT4lIAogIGdncmFwaChsYXlvdXQgPSAia2siKSArCiAgICBnZW9tX2VkZ2VfbGluaygpICsKICAgIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gQXJ0aXN0TmFtZSksIHJlcGVsID0gVCwgZGF0YSA9IH4gLnggJT4lIGZpbHRlcihBcnRpc3ROYW1lID09ICJOdWJsdSIpKQpgYGAKClRpcHUgb2x1bGlzdXN0IGdyYWFmaXMgdsO1aWIgbsOkaWRhdGEgbWl0dXQgbW9vZGksIGt1aWQga8O1aWtpIG5laWQgbMOkaGVuZW1pc2kgdsO1ZXRha3NlIGtva2t1IMO8aGUgdGVybWluaWdhIHRzZW50cmFhbHN1cy4gS8O1aWcgbGlodHNhbSB0c2VudHJhYWxzdXNlIG3DtcO1dCwgdGlwdSB2YWxlbnRzLCBsb2ViIGtva2t1IGvDtWlrIHRpcHUgw7xoZW5kdXNlZC4gU3V1bmF0dWQgZ3JhYWZpcyB2w7VpYiBzZWRhIHRlaGEgbmlpIHNpc3NlIGt1aSB2w6RsamFwb29sZSDDvGhlbmR1c3RlZ2EgZXJhbGRpLiBSLXMgc2FhYiBzZWRhIGFydnV0YWRhIGvDpHN1Z2EgYGNlbnRyYWxpdHlfZGVncmVlYC4gS2VlcnVrYW0gdHNlbnRyYWFsc3VzbcO1w7V0IG9uICpiZXR3ZWVubmVzcyBjZW50cmFsaXR5KiwgbWlzIG3DvMO8ZGFiIGt1aSBzdXVyIG9zYSDDvGhlbmR1c3Rlc3QgZ3JhYWZpIHRpcHB1ZGUgdmFoZWwgbMOkYmliIHZhc3RhdmF0IHRpcHB1LiBFcmluZXZhaWQgYWxnb3JpdG1lIG9uIGFnYSB2ZWVsICh2dCBgP2NlbnRyYWxpdHlfZGVncmVlKWAKCmBgYHtyfQojVmFhdGFtZSBwdW5rdGlkZSBjZW50cmFsaXR5dCBqYSBkZWdyZWVkIHN1dXJ1c3RlIGphIHbDpHJ2aWRlIGFiaWdhIGphIGZpbHRyZWVyaW1lIHbDpGxqYSBuZWVkLCBrdXMgY2VudHJhbGl0eSBvbiBzdXVyZW0ga3VpIDEwCmcgJT4lIAogIGFjdGl2YXRlKG5vZGVzKSAlPiUgCiAgbXV0YXRlKERlZ3JlZSA9IGNlbnRyYWxpdHlfZGVncmVlKCkpICU+JSAKICBtdXRhdGUoQ2VudHJhbGl0eSA9IGNlbnRyYWxpdHlfZGVncmVlKCkpICU+JSAKICBnZ3JhcGgobGF5b3V0ID0gImtrIikgKyAKICAgIGdlb21fZWRnZV9saW5rKCkgKyAKICAgIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IERlZ3JlZSwgY29sb3IgPSBDZW50cmFsaXR5KSkgKwogICAgZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IEFydGlzdE5hbWUpLCBkYXRhID0gfiAueCAlPiUgZmlsdGVyKENlbnRyYWxpdHkgPiAxMCkpCmBgYAoKT21hZHVzaSB2w7VpYiB2YWFkYXRhIGthIHNlcnZhZGVsLiBLYSBuZW5kZSBwdWh1bCBzYWFiIGxlaWRhIHNlcnZhZCwgbWlzIMO8aGVkdXZhZCBrb25rcmVldHNlIHRpcHVnYSB2w7VpIHNlcnZhc2lkIG1pcyBvbiB2w7VyZ3VzdGlrdXMga2Vza3NlbCBrb2hhbC4gTmVpZCBmdW5rdHNpb29uZSBzYWFiIGthc3V0YWRhIGvDpHNrdWRlZ2EgYGVkZ2VfaXNfZnJvbWAgamEgYGNlbnRyYWxpdHlfZWRnZV9iZXR3ZWVubmVzc2AuCgpgYGB7cn0KI0V0IHBhcmVtaW5pIG7DpGhhIHNlcnZhc2lkLCBtaXMgw7xoZW5kdXZhZCBudWJsdWdhCmcgJT4lIAogIGFjdGl2YXRlKGVkZ2VzKSAlPiUgCiAgbXV0YXRlKGVOdWJsdSA9IGVkZ2VfaXNfZnJvbSgoZyAlPiUgYWN0aXZhdGUobm9kZXMpICU+JSBwdWxsKEFydGlzdE5hbWUpKSA9PSAiTnVibHUiKSkgJT4lIAogIGdncmFwaChsYXlvdXQgPSAia2siKSArIAogICAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gZU51Ymx1KSkgKyAKICAgIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgICBzY2FsZV9lZGdlX3dpZHRoX2Rpc2NyZXRlKHJhbmdlID0gYygwLjIsIDEpKQoKI1NlcnZhZGUgY2VudHJhbGl0eSBtw7XDtWR1IGt1anV0YW1pbmUKZyAlPiUgCiAgYWN0aXZhdGUoZWRnZXMpICU+JSAKICBtdXRhdGUoQ2VudHJhbGl0eSA9IGNlbnRyYWxpdHlfZWRnZV9iZXR3ZWVubmVzcygpKSAlPiUgCiAgZ2dyYXBoKGxheW91dCA9ICJrayIpICsgCiAgICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBDZW50cmFsaXR5KSkgKyAKICAgIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgICBzY2FsZV9lZGdlX3dpZHRoX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAuMiwgMS41KSkKYGBgCgpHcmVlZmkgdGlwcGUgb24ga2Ega2FzdWxpayBrbGFzdGVyZGFkYSwgZXQgw7xsZXMgbGVpZGEgbG9vZ2lsaXNlZCBhbGFtZ3JhYWZpZC4gTGlodHNhaW0ga2xhc3RlcmRhbWlzZSB2aWlzIG9uIGxlaWRhIHNpZHVzYWQgYWxhbWdyYWFmaWQsIG1pZGEgc2FhYiB0ZWhhIGvDpHN1Z2EgYGdyb3VwX2NvbXBvbmVudHNgLiBLZWVydWthbWFpZCBrYWxzdGVyZHVzaSBvbiBrYSBww6RyaXMgcGFsanUgc2FhZGF2YWwgamEgw7xoZSBzZWxsaXNlIG7DpGl0ZWtzIHbDtWlrcyBvbGxhIGBncm91cF9sb3V2YWluYCAodGVpc3RlIGphb2tzIHV1cmkgYD9ncm91cF9sb3V2YWluYCkuCgpgYGB7cn0KZyAlPiUgCiAgYWN0aXZhdGUobm9kZXMpICU+JSAKICBmaWx0ZXIoIW5vZGVfaXNfaXNvbGF0ZWQoKSkgJT4lIAogIG11dGF0ZShDQyA9IGdyb3VwX2NvbXBvbmVudHMoKSkgJT4lIAogIGdncmFwaChsYXlvdXQgPSAia2siKSArIAogICAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yID0gQ0MpKSAgCgpzIDwtIGcgJT4lIAogIGFjdGl2YXRlKGVkZ2VzKSAlPiUgCiAgZmlsdGVyKGZyb20gPiAzICYgdG8gPiAzKQoKCgpnICU+JSAKICBhY3RpdmF0ZShub2RlcykgJT4lIAogIGZpbHRlcighbm9kZV9pc19pc29sYXRlZCgpKSAlPiUgCiAgbXV0YXRlKEdMID0gYXMuZmFjdG9yKGdyb3VwX2xvdXZhaW4oKSkpICU+JSAKICBnZ3JhcGgobGF5b3V0ID0gImtrIikgKyAKICAgIGdlb21fZWRnZV9saW5rKCkgKwogICAgZ2VvbV9ub2RlX3BvaW50KGFlcyhjb2xvciA9IEdMKSkrCiAgICBnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gQXJ0aXN0TmFtZSkgZGF0YSA9ICkKCmBgYAoKIyMjIyDDnGxlc2FuZGVkCgotICAgVmlzdWFsaXNlZXJpIGFudHVkIGFuZG1lc3Rpa3Uga8O1aWdlIHN1dXJlbSBzaWR1cyBrb21wb25lbnQuIE7DpGl0YSBuaW1lZGVnYSBrw7VpayBhcnRpc3RpZCwga2VsbGVsIG9uIHJvaGtlbSBrdWkgMyBrb29zdMO2w7ZkLiBOw6RpdGEgcHVua3RpIHTDvMO8YmlnYSDDpHJhIHNlbGxlIHNlbGxlcyBncmFhZmlzIG9sZXZhZCBrbGFzdGlyaWQgKExvdXZhaW4taSBhbGdvcml0bWkgasOkcmdpKQoKYGBge3J9CmcgJT4lIAogIGFjdGl2YXRlKG5vZGVzKSAlPiUKICBtdXRhdGUoQ0MgPSBncm91cF9jb21wb25lbnRzKCkpICU+JSAKICBmaWx0ZXIoQ0M9PTEpICU+JSAKICBtdXRhdGUoRGVncmVlID0gY2VudHJhbGl0eV9kZWdyZWUoKSkgJT4lIAogIG11dGF0ZShHTCA9IGFzLmZhY3Rvcihncm91cF9sb3V2YWluKCkpKSAlPiUgCiAgZ2dyYXBoKGxheW91dCA9ICJrayIpICsgCiAgICBnZW9tX2VkZ2VfbGluaygpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoc2hhcGU9R0wpKSsKICAgIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBBcnRpc3ROYW1lKSwgZGF0YSA9IH4gLnggJT4lIGZpbHRlcihEZWdyZWUgPiAzKSkKYGBgCgoKIyMgS29kdW5lIMO8bGVzYW5uZQoKRmFpbGlzIGBzcG90aWZ5X2ZvcmVpZ24uUkRhdGFgIG9uIHNhbWFzdWd1c2VkIGFuZG1lZCBhZ2EgdsOkbGlzYXJ0aXN0aWRlIGtvaHRhLiBQcm9vdmkgc2VhbHQgdsOkbGphIHR1dWEgbcO1bmkgc2ludSBhcnZhdGVzIGh1dml0YXYgc2VvcyDDvGhlIHZpc3VhbGlzYXRzaW9vbmlnYS4gRXNpdGFkYSB0dWxla3MgbmlpIGdyYWFmaSBnZW5lcmVlcml2IGtvb2Qga3VpIGthIHNlbGdpdHVzLCBtaWRhIHRlIHBpbGRpbHQgdsOkbGphIGx1Z2VzaXRlLiBQaWxkaSBwdWh1bCBoaW5kYW4sIG1pbGxpc2VpZCB2w7V0dGVpZCB0ZSBvbGV0ZSBrYXN1dGFudWQgcGlsZGkgZXR0ZXZhbG1pc3RhbWlzZWwsIGthc3V0YXR1ZCB2w7Vpa3Mgb2xsYSB2w6RoZW1hbHQgw7xrcyBlbGVtZW50IGvDtWlnZXN0IGrDpHJnbmV2YXN0LgoKLSAgIE9uIGxlaXR1ZCBzb2JpdiBwdW5rdGlkZSBwYWlndXR1cwoKLSAgIE9uIG11dWRldHVkIHRpcHB1ZGUgZ3JhYWZpbGlzaSBvbWFkdXNpCgotICAgT24gbXV1ZGV0dWQgc2VydmFkZSBncmFhZmlsaXNpIG9tYWR1c2kKCi0gICBPbiBuw6RpZGF0dWQgw6RyYSBodXZpcGFra3V2YW1hZCB0aXB1ZCBuaW1lIHbDtWkgbcO1bmUgbXV1IHRla3N0aWdhCgotICAgT24ga2FzdXRhdHVkIGdyYWFmaSBhbGdvcml0bWUsIGFuZG1ldGUgZWVsdMO2w7Z0bHVzZWtzIHbDtWkgZ3JhYWZpbGlzdGUgZWxlbWVudGlkZSBsaXNhbWlzZWtzCgpMaXNha3MgdGVobmlsaXNlbGUgcG9vbGVsZSBoaW5kYW4gasOkcmVsZHVzZSBqYSBncmFhZmlrdSBrb2trdWxhbmdldnVzdCBuaW5nIGdyYWFmaWt1IHZpc3VhYWxzZXQgdm9ybWlzdHVzdC4K